/*	--- 修改响应 --- 

既然我们能通过【Filter】修改【HttpServletRequest】，自然也能修改【HttpServletResponse】，因为这两者都是【接口】。

我们来看一下在【什么情况下】,我们需要【修改HttpServletResponse】。

假设我们编写了【一个Servlet】，但由于【业务逻辑】比较复杂，【处理该请求】需要耗费很长的时间： */
@WebServlet(urlPatterns = "/slow/hello")
public class HelloServlet extends HttpServlet {
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {

		resp.setContentType("text/html");

		// 模拟消耗1s
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			//..
		}

		PrintWriter pw = resp.getWriter();
		pw.write("<h1>Hello, world!</h1>");
		pw.flush();
	}
}


/*
好消息是【每次返回的响应内容】是固定的，因此，如果我们能使用【缓存】将【结果】缓存起来，就可以【大大提高Web应用程序的运行效率】。

【缓存逻辑】最好【不要在Servlet内部实现】，因为我们希望能【复用缓存逻辑】，所以，编写一个【class CacheFilter】最合适： */
@WebFilter("/slow/*")
public class CacheFilter implements Filter {
	// String Path 映射 byte[]的缓存
	private Map<String, byte[]> cache = new ConcurrentHashMap<>();
		// cache 存放在 @WebFilter()中

	public void doFilter(ServletRequest request, ServletResponse resp, FilterChain chain) throws IOException,ServletException {

		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;

		// 获取Path
		String url = req.getRequestURI();

		// 获取Cache内容
		byte[] data = this.cache.get(url);

		resp.setHeader("X-Cache-Hit", data == null ? "No" : "Yes");

		if (data == null) {
			// Cache未找到的情况下, 构造一个伪造的Response
			CachedHttpServletResponse wrapper = new CachedHttpServletResponse(resp); // 拦截resp

			// 让【下游组件】写入数据到【伪造的Response】
			chain.doFilter(request, wrapper); // 包装resp

			// 从【伪造的Response】中读取【写入的内容】,并【放入缓存】
			data = wrapper.getContent(); // 把拦截内容return回去

			cache.put(url, data); // 放入<String, byte[]> cache中
		}

		// 写入到【原始的Response】
		ServletOutputStream output = resp.getOutputStream();

		output.write(data); // 已有cache的情况下,直接使用
		output.flush();

	}
}

/*
【实现缓存的关键】在于: 

调用doFilter()时，我们不能传入【原始的HttpServletResponse】，因为这样就会【写入Socket】，我们也就无法获取【下游组件】写入的内容。

如果我们传入的是【“伪造”的HttpServletResponse】，让【下游组件】写入到我们【预设的ByteArrayOutputStream】，我们就【“截获”了下游组件写入的内容】

于是，就可以把【内容】缓存起来，再通过【原始的HttpServletResponse实例】写入到【网络】。

---------------------------

这个【class CachedHttpServletResponse】实现如下： */
class CachedHttpServletResponse extends HttpServletResponseWrapper {

    private boolean open = false;
    private ByteArrayOutputStream output = new ByteArrayOutputStream(); // 存

    public CachedHttpServletResponse(HttpServletResponse response) {
        super(response);
    }

    // 【获取Writer】
    public PrintWriter getWriter() throws IOException {
        if (open) {
            throw new IllegalStateException("Cannot re-open writer!");
        }

        open = true;

        return new PrintWriter(output, false, StandardCharsets.UTF_8);
        	// (ByteArrayOutputStream, false, StandardCharsets.UTF_8)
    }

    // 【获取OutputStream】
    public ServletOutputStream getOutputStream() throws IOException {
        if (open) {
            throw new IllegalStateException("Cannot re-open output stream!");
        }

        open = true;

        return new ServletOutputStream() {
            public boolean isReady() {
                return true;
            }

            public void setWriteListener(WriteListener listener) {
            	//...
            }

            // 【实际写入ByteArrayOutputStream】
            public void write(int b) throws IOException {
                output.write(b);
            }
        };
    }

    // return 写入的byte[]
    public byte[] getContent() {
        return output.toByteArray();
    }
}

/*

可见，如果我们想要修改【Response】，

可以通过【extends HttpServletResponseWrapper; 构造获取super(resp)】 构造 【一个“伪造”的HttpServletResponse - CachedHttpServletResponse】，这样就能【拦截】到【写入的数据】。

【修改Response】时，最后不要忘记把【数据】写入【原始的HttpServletResponse实例】。


这个【CacheFilter】同样是【一个“可插拔”组件】，【CacheFilter】是否【启用】不影响【Web应用程序的其他组件（Filter、Servlet）】。


==================================================================

#	练习

通过Filter修改响应 */



/*================================================================

#	----- 修改Response の 小结 ----- 


1. 借助【HttpServletResponseWrapper】，我们可以在【Filter】中, 实现对【原始HttpServletRequest】的修改。
(即: 借助Wrapper对req进行"封装+修改")



*/
























